/*
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/*
 *    SingleIndex.java
 *    Copyright (C) 2003-2012 University of Waikato, Hamilton, New Zealand
 *
 */

package weka.core;

import java.io.Serializable;

/**
 * Class representing a single cardinal number. The number is set by a string
 * representation such as:
 * <P>
 *
 * <code>
 *   first
 *   last
 *   1
 *   3
 * </code>
 * <P>
 * The number is internally converted from 1-based to 0-based (so methods that
 * set or get numbers not in string format should use 0-based numbers).
 *
 * @author Eibe Frank (eibe@cs.waikato.ac.nz)
 * @version $Revision$
 */
public class SingleIndex implements Serializable, CustomDisplayStringProvider {

    /** for serialization. */
    static final long serialVersionUID = 5285169134430839303L;

    /** Record the string representation of the number. */
    protected /* @non_null spec_public@ */ String m_IndexString = "";

    /** The selected index. */
    protected /* @ spec_public @ */ int m_SelectedIndex = -1;

    /**
     * Store the maximum value permitted. -1 indicates that no upper value has been
     * set
     */
    protected /* @ spec_public @ */ int m_Upper = -1;

    /**
     * Default constructor.
     *
     */
    // @ assignable m_IndexString, m_SelectedIndex, m_Upper;
    // @ ensures m_SelectedIndex == -1;
    // @ ensures m_Upper == -1;
    public SingleIndex() {
    }

    /**
     * Constructor to set initial index.
     *
     * @param index the initial index
     * @throws IllegalArgumentException if the index is invalid
     */
    // @ assignable m_IndexString, m_SelectedIndex, m_Upper;
    // @ ensures m_IndexString == index;
    // @ ensures m_SelectedIndex == -1;
    // @ ensures m_Upper == -1;
    public SingleIndex(/* @non_null@ */ String index) {

        setSingleIndex(index);
    }

    /**
     * Sets the value of "last".
     *
     * @param newUpper the value of "last"
     */
    // @ assignable m_Upper, m_IndexString, m_SelectedIndex;
    // @ ensures newUpper < 0 ==> m_Upper == \old(m_Upper);
    // @ ensures newUpper >= 0 ==> m_Upper == newUpper;
    public void setUpper(int newUpper) {

        if (newUpper >= 0) {
            m_Upper = newUpper;
            setValue();
        }
    }

    /**
     * Gets the string representing the selected range of values.
     *
     * @return the range selection string
     */
    // @ ensures \result == m_IndexString;
    public /* @pure@ */ String getSingleIndex() {

        return m_IndexString;
    }

    /**
     * Sets the index from a string representation. Note that setUpper() must be
     * called for the value to be actually set
     *
     * @param index the index set
     * @throws IllegalArgumentException if the index was not well formed
     */
    // @ assignable m_IndexString, m_SelectedIndex;
    // @ ensures m_IndexString == index;
    // @ ensures m_SelectedIndex == -1;
    public void setSingleIndex(/* @non_null@ */ String index) {

        m_IndexString = index;
        m_SelectedIndex = -1;
    }

    /**
     * Constructs a representation of the current range. Being a string
     * representation, the numbers are based from 1.
     * 
     * @return the string representation of the current range
     */
    // @ also signals (RuntimeException e) \old(m_Upper) < 0;
    // @ ensures \result != null;
    public /* @pure@ */ String toString() {

        if (m_IndexString.equals("")) {
            return "No index set";
        }
        if (m_Upper == -1) {
            throw new RuntimeException("Upper limit has not been specified");
        }
        return m_IndexString;
    }

    /**
     * Gets the selected index.
     *
     * @return the selected index
     * @throws RuntimeException if the upper limit of the index hasn't been defined
     */
    // @ requires m_Upper >= 0;
    // @ requires m_IndexString.length() > 0;
    // @ ensures \result == m_SelectedIndex;
    public /* @pure@ */ int getIndex() {

        if (m_IndexString.equals("")) {
            throw new RuntimeException("No index set");
        }
        if (m_Upper == -1) {
            throw new RuntimeException("No upper limit has been specified for index");
        }
        return m_SelectedIndex;
    }

    /**
     * Creates a string representation of the given index.
     *
     * @param index the index to turn into a string. Since the index will typically
     *              come from a program, indices are assumed from 0, and thus will
     *              have 1 added in the String representation.
     * @return the string representation
     */
    // @ requires index >= 0;
    public static /* @pure non_null@ */ String indexToString(int index) {

        return "" + (index + 1);
    }

    /**
     * Translates a single string selection into it's internal 0-based equivalent.
     */
    // @ assignable m_SelectedIndex, m_IndexString;
    protected void setValue() {

        if (m_IndexString.equals("")) {
            throw new RuntimeException("No index set");
        }
        if (m_IndexString.toLowerCase().equals("first")) {
            m_SelectedIndex = 0;
        } else if (m_IndexString.toLowerCase().equals("last")) {
            m_SelectedIndex = m_Upper;
        } else {
            m_SelectedIndex = Integer.parseInt(m_IndexString) - 1;
            if (m_SelectedIndex < 0) {
                m_IndexString = "";
                throw new IllegalArgumentException("Index must be greater than zero");
            }
            if (m_SelectedIndex > m_Upper) {
                m_IndexString = "";
                throw new IllegalArgumentException("Index is too large");
            }
        }
    }

    /**
     * Returns the custom display string.
     * 
     * @return the string
     */
    public String toDisplay() {
        return getSingleIndex();
    }

    /**
     * Main method for testing this class.
     *
     * @param argv one parameter: a test index specification
     */
    // @ requires \nonnullelements(argv);
    public static void main(/* @non_null@ */ String[] argv) {

        try {
            if (argv.length == 0) {
                throw new Exception("Usage: SingleIndex <indexspec>");
            }
            SingleIndex singleIndex = new SingleIndex();
            singleIndex.setSingleIndex(argv[0]);
            singleIndex.setUpper(9);
            System.out.println("Input: " + argv[0] + "\n" + singleIndex.toString());
            int selectedIndex = singleIndex.getIndex();
            System.out.println(selectedIndex + "");
        } catch (Exception ex) {
            ex.printStackTrace();
            System.out.println(ex.getMessage());
        }
    }
}
